﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using SkinnedModel;
using RainEngine.SceneManagement;

namespace RainEngine.SceneNodes
{
    /// <summary>
    /// 支持蒙皮动画的模型类
    /// </summary>
    public class SkinningModelSceneNode :Renderable3DSceneNode
    {
        #region 字段

        /// <summary>
        /// 模型
        /// </summary>
        Model model;

        /// <summary>
        /// effect数组
        /// </summary>
        private SkinnedEffect[] effects;        

        /// <summary>
        /// 模型使用的图元数量
        /// </summary>
        int primitives;

        /// <summary>
        /// 模型名称，包含路径
        /// </summary>
        string modelAssetName;

        /// <summary>
        /// 各部件转换矩阵数组
        /// </summary>
        Matrix[] modelTransforms;

        /// <summary>
        /// 模型的全局包围球
        /// </summary>
        BoundingSphere originalSphere;

        /// <summary>
        /// 保存动画片段名称的集合
        /// </summary>
        public List<string> animationList;

        /// <summary>
        /// bone的变换矩阵数组
        /// </summary>
        Matrix[] boneTransforms;

        /// <summary>
        /// bone的绝对变换矩阵数组
        /// </summary>
        Matrix[] worldTransforms;

        /// <summary>
        /// 变换顶点位置的最终矩阵，这个矩阵要传递到effect中进行处理
        /// </summary>
        Matrix[] skinTransforms;

        /// <summary>
        /// 动画数据
        /// </summary>
        SkinningData skinningData;
        
        /// <summary>
        /// 当前播放的动画片段
        /// </summary>
        AnimationClip currentAnimationClip;

        /// <summary>
        /// 当前播放的动画片段的索引
        /// </summary>
        int currentAnimationId=-1;

        /// <summary>
        /// 当前动画片段经历的时间，默认为动画片段的开始时刻
        /// </summary>
        TimeSpan currentTimeValue;        

        /// <summary>
        /// 当前动画片段的当前关键帧索引，默认为第一帧
        /// </summary>
        int currentKeyframe;

        /// <summary>
        /// 自定义的bone的变换矩阵数组
        /// </summary>
        Matrix[] customBoneTransforms;

        #endregion

        #region 属性

        /// <summary>
        /// 获取effect数组
        /// </summary>
        public SkinnedEffect[] Effects
        {
            get { return effects; }
        }     

        /// <summary>
        /// 动画片段是否已经结束
        /// </summary>
        public bool IsAnimationFinished
        {
            get
            {
                return (currentTimeValue >= currentAnimationClip.Duration);
            }
        }        

        /// <summary>
        /// 是否可以循环播放动画
        /// </summary>
        public bool EnableAnimationLoop
        {
            get;set;
        }

        /// <summary>
        /// 获取或设置动画播放速度
        /// </summary>
        public float AnimationSpeed
        {
            get;set;
        }        

        /// <summary>
        /// 获取或设置自定义的bone的变换矩阵数组
        /// </summary>
        public Matrix[] CustomBoneTransforms
        {
            get { return customBoneTransforms; }
            set { customBoneTransforms = value; }
        }

        #endregion        

        /// <summary>
        /// 创建一个SkinningModelSceneNode对象。
        /// </summary>
        /// <param name="engine">引擎</param>
        /// <param name="setScene">所属场景</param>
        /// <param name="modelAssetName">模型名称</param>
        public SkinningModelSceneNode(GraphicsDevice graphicsDevice, ContentManager setContent, Scene setScene, string modelAssetName)
            : base(graphicsDevice, setContent, setScene)
        {
            this.modelAssetName = modelAssetName;
            animationList = new List<string>();
            EnableAnimationLoop = true;
            AnimationSpeed = 1.0f;
        }   
     
        /// <summary>
        /// 初始化
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();
            this.UpdateOrder = SceneManagement.SceneNodeOrdering.SceneNode.GetValue();

            // 创建一个材质数组保存所有ModelMeshPart的材质
            List<SkinnedEffect> effectList = new List<SkinnedEffect>();

            // 遍历模型的所有ModelMesh
            foreach (ModelMesh mesh in model.Meshes)
            {
                //根据每个ModelMesh的包围球计算模型的全局包围球
                BoundingSphere meshSphere = new BoundingSphere(Vector3.Zero, 0); ;
                mesh.BoundingSphere.Transform(ref modelTransforms[mesh.ParentBone.Index], out meshSphere);
                BoundingSphere.CreateMerged(ref originalSphere, ref meshSphere, out originalSphere);

                // 遍历当前ModelMesh的所有ModelMeshPart
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    // 获取模型使用的图元数量
                    primitives += meshPart.PrimitiveCount;

                    BasicEffect be = meshPart.Effect as BasicEffect;
                    if (be != null)
                    {
                        SkinnedEffect effect = new SkinnedEffect(graphicsDevice);
                        effect.Texture = be.Texture;
                        effect.PreferPerPixelLighting = be.PreferPerPixelLighting;
                        effect.EnableDefaultLighting();
                        effect.AmbientLightColor = be.AmbientLightColor;
                        effect.DiffuseColor = be.DiffuseColor;
                        effect.SpecularColor = be.SpecularColor;
                        effect.SpecularPower = be.SpecularPower;
                        effect.EmissiveColor = be.EmissiveColor;
                        effect.Alpha = be.Alpha;
                        effectList.Add(effect);
                    }
                }
            }
            effects = effectList.ToArray();
            this.Pose.LocalSize = originalSphere.Radius * 2;
        }

        internal override void LoadContent()
        {
            model = content.Load<Model>(modelAssetName);
            modelTransforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(modelTransforms);

            // 从模型的Tag属性中提取SkinningData信息
            skinningData = model.Tag as SkinningData;

            if (skinningData == null)
                throw new InvalidOperationException
                    ("这个模型的Tag属性不包含SkinningData");
            
            // 初始化矩阵数组
            customBoneTransforms = new Matrix[skinningData.BindPose.Count];
            boneTransforms = new Matrix[skinningData.BindPose.Count];
            worldTransforms = new Matrix[skinningData.BindPose.Count];
            skinTransforms = new Matrix[skinningData.BindPose.Count];

            // 将动画片段名称添加到一个集合，这样我们就可以使用索引访问动画片段
            foreach (KeyValuePair<string, AnimationClip> entry in skinningData.AnimationClips)
            {
                animationList.Add(entry.Key);
            }

            // 初始化bone变换
            skinningData.BindPose.CopyTo(boneTransforms, 0);
            
            // 初始化customBoneTransforms矩阵数组
            for (int i = 0; i < customBoneTransforms.Length; i++)
            {
                customBoneTransforms[i] = Matrix.Identity;
            }
        }        

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            // 更新动画使用的三个矩阵
            UpdateAnimation(gameTime);
        }

        /// <summary>
        /// 更新动画使用的三个矩阵
        /// </summary>
        /// <param name="time">时间</param>
        public void UpdateAnimation(GameTime time)
        {
            currentTimeValue += new TimeSpan((long)(time.ElapsedGameTime.Ticks * AnimationSpeed));

            // 动画处理的任务分成三个部分
            if (currentAnimationClip != null)
            {
                // --------------------------------------------------------
                // 第一部分：根据当前播放时间更新boneTransforms矩阵数组
                // ---------------------------------------------------------

                // 如果播放时间大于动画片段长度且允许循环播放，则重新播放动画片段
                if (currentTimeValue > currentAnimationClip.Duration && EnableAnimationLoop)
                {
                    ResetAnimation();
                }                
                
                IList<Keyframe> keyframes = currentAnimationClip.Keyframes;
                
                // 读取截止到播放时间内的关键帧
                while (currentKeyframe < keyframes.Count && keyframes[currentKeyframe].Time <= currentTimeValue)
                {
                    int boneIndex = keyframes[currentKeyframe].BoneIndex;
                    boneTransforms[boneIndex] = keyframes[currentKeyframe].Transform;
                    currentKeyframe++;
                }
            }

            // 将自定义的bone变换施加在所有bone上
            for (int i = 0; i < customBoneTransforms.Length; i++)
            {
                worldTransforms[i] = customBoneTransforms[i] * boneTransforms[i];
            }

            // --------------------------------------------------------
            // 第二部分：计算所有bone的绝对变换矩阵
            // --------------------------------------------------------

            // 计算根节点绝对矩阵
            worldTransforms[0] = worldTransforms[0] * Pose.WorldMatrix ;

            // 计算子节点绝对矩阵
            for (int boneIndex = 1; boneIndex < worldTransforms.Length; boneIndex++)
            {
                int parentBone = skinningData.SkeletonHierarchy[boneIndex];
                worldTransforms[boneIndex] = worldTransforms[boneIndex] * worldTransforms[parentBone];
            }

            // --------------------------------------------------------
            // 第三部分：计算变换顶点的矩阵
            // --------------------------------------------------------
            // 顶点坐标需要乘以bone的逆矩阵才能转换到bone的坐标空间中

            for (int boneIndex = 0; boneIndex < skinTransforms.Length; boneIndex++)
            {
                skinTransforms[boneIndex] = skinningData.InverseBindPose[boneIndex] * worldTransforms[boneIndex];
            }
        }

        /// <summary>
        /// 重置动画片段
        /// </summary>
        private void ResetAnimation()
        {
            currentTimeValue = TimeSpan.Zero;
            currentKeyframe = 0;
            skinningData.BindPose.CopyTo(boneTransforms, 0);
        }


        public override int Draw(GameTime gameTime, Matrix ViewMatrix, Matrix ProjectionMatrix, Vector3 CameraPosition)
        {

            int effectCounter = 0;

            // 遍历模型的所有ModelMesh
            for (int mCounter = 0; mCounter < model.Meshes.Count; mCounter++)
            {
                ModelMesh mesh = model.Meshes[mCounter];

                Matrix world = modelTransforms[mesh.ParentBone.Index] * Pose.WorldMatrix;

                int count = mesh.MeshParts.Count;
                // 遍历当前ModelMesh的所有ModelMeshPart
                for (int i = 0; i < count; i++)
                {
                    ModelMeshPart part = mesh.MeshParts[i];

                    effects[effectCounter].Techniques[0].Passes[0].Apply();
                    effects[effectCounter].SetBoneTransforms(skinTransforms);
                    effects[effectCounter].World = world;
                    effects[effectCounter].View = ViewMatrix;
                    effects[effectCounter].Projection = ProjectionMatrix;

                    graphicsDevice.Indices = part.IndexBuffer;
                    graphicsDevice.SetVertexBuffer(part.VertexBuffer);
                    graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, part.VertexOffset, 0, part.NumVertices, part.StartIndex, part.PrimitiveCount);

                    effectCounter++;
                }


            }
            return 1;
        }

        public override void SetClipPlane(Vector4? Plane)
        {
            //effect.ClipPlane = Plane.Value;
        }        

        /// <summary>
        /// 播放指定名称的动画片段，循环播放，无需等待上一个动画结束
        /// </summary>
        /// <param name="animationName"></param>
        public void SetAnimationClip(string animationName)
        {
            SetAnimationClip(animationName, true, false);
        }

        /// <summary>
        /// 播放指定名称的动画片段
        /// </summary>
        /// <param name="animationId">动画片段编号</param>
        /// <param name="enableLoop">是否循环播放动画片段</param>
        /// <param name="waitFinish">是否等待当前动画片段结束</param>
        public void SetAnimationClip(string animationName, bool enableLoop, bool waitFinish)
        {
            if(animationList.IndexOf (animationName)==-1)
                throw new InvalidOperationException("指定的动画片段不存在");
            else
                currentAnimationId = animationList.IndexOf(animationName);
            
            if (waitFinish && !IsAnimationFinished)
                return;
            currentAnimationClip = skinningData.AnimationClips[animationName];
            EnableAnimationLoop = enableLoop;  
        }

        /// <summary>
        /// 播放指定编号的动画片段，循环播放，无需等待上一个动画结束
        /// </summary>
        /// <param name="animationId">动画片段编号</param>
        public void SetAnimationClip(int animationId)
        {
            SetAnimationClip(animationId, true, false);
        }

        /// <summary>
        /// 播放指定编号的动画片段
        /// </summary>
        /// <param name="animationId">动画片段编号</param>
        /// <param name="enableLoop">是否循环播放动画片段</param>
        /// <param name="waitFinish">是否等待当前动画片段结束</param>
        public void SetAnimationClip(int animationId, bool enableLoop, bool waitFinish)
        {
            if (currentAnimationId != animationId&&animationId >=0&&animationId<animationList .Count)
            {
                if (waitFinish && !IsAnimationFinished)
                    return;

                currentAnimationClip = skinningData.AnimationClips[animationList[animationId]];
                EnableAnimationLoop = enableLoop;
                currentAnimationId = animationId;
            }
        }
    }
}
